﻿using System;
using System.IO;
using System.Xml.Serialization;

namespace Daystate.ConfigurationManager
{
    /// <summary>
    /// Enables saving configuration class (inhirited by ConfigurationClass) using serialization and deserialization. 
    /// </summary>
    /// <typeparam name="T">Configuration type (inhirited by ConfigurationClass)</typeparam>
    public class ConfigManager<T> where T : ConfigurationClass
    {
        #region ========== Singleton ==========

        static object locker = new object();
        static ConfigManager<T> instance = null;

        public static ConfigManager<T> Instance
        {
            get
            {
                lock (locker)
                {
                    if (instance == null)
                    {
                        Instance = new ConfigManager<T>(typeof(T).Name + ".xml");
                    }
                    return instance;
                }
            }
            protected set
            {
                lock (locker)
                {
                    instance = value;
                }
            }
        }

        #endregion

        #region ========== Constructors ==========

        /// <summary>
        /// 
        /// </summary>
        /// <param name="configFilePath">Configuration file path.</param>
        protected ConfigManager(string configFilePath)
        {

            if (IsFilePathValid(configFilePath))
            {
                this.configurationFilePath = configFilePath;
            }
            else
                throw new ArgumentNullException("Invalid configuration file path.");

            // Try loading up configuration
            try
            {
                Load();
            }
            catch (Exception)
            {
                // Create new instance of configuration
                if (this.configuration == null)
                    this.configuration = Activator.CreateInstance<T>();

                this.configuration.Initialize();
                this.Save();
            }
        }

        #endregion

        #region ========== Methods ==========

        /// <summary>
        /// 
        /// </summary>
        /// <param name="configFilePath"></param>
        public static ConfigManager<T> CreateInstance(string configFilePath)
        {
            Instance = new ConfigManager<T>(configFilePath);
            return Instance;
        }

        /// <summary>
        /// Validate configuration file path.
        /// </summary>
        /// <param name="filePath">Configuration file path.</param>
        /// <returns>Is path valid.</returns>
        bool IsFilePathValid(string filePath)
        {
            return !string.IsNullOrWhiteSpace(filePath);
        }

        /// <summary>
        /// Saves configuration into the file.
        /// </summary>
        public void Save()
        {
            SerializeToXML();
            Console.WriteLine("Serialized!");
        }

        /// <summary>
        /// Loads up configuration file.
        /// </summary>
        /// <returns></returns>
        public void Load()
        {
            // Use XML deserialisation            
            Console.WriteLine("DeserializeFromXML");
            DeserializeFromXML();
        }

        /// <summary>
        /// Serializes configuration instance into XML file.
        /// </summary>
        /// <returns>Returns serialisation result.</returns>
        private bool SerializeToXML()
        {
            // Check is config not null.
            if (this.configuration == null)
                throw new ConfigurationException("Config cannot be null!");

            // Check is provided config file path correct.
            if (string.IsNullOrWhiteSpace(this.configurationFilePath))
                throw new IOException("Provided configuration file path is incorrect!");

            // Create serializer and writing stream. 
            #region Overriding xml element attributes
            //XmlAttributeOverrides aor = new XmlAttributeOverrides();
            //XmlAttributes listAttribs = new XmlAttributes();
            //listAttribs.XmlElements.Add(new XmlElementAttribute("ConfigListProperty", typeof(ConfigListProperty<string>)));
            //listAttribs.XmlElements.Add(new XmlElementAttribute("ConfigProperty", typeof(ConfigProperty)));
            //aor.Add(typeof(T), listAttribs);
            #endregion

            XmlSerializer serializer = new XmlSerializer(typeof(T)); //, aor, null, new XmlRootAttribute("TestConfiguration"), Namespace);
            bool serialisationResult = false;
            try
            {
                using (TextWriter stream = new StreamWriter(this.configurationFilePath))
                {
                    serializer.Serialize(stream, this.configuration);
                }
                // Serialisation was successfull
                serialisationResult = true;
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                serialisationResult = false;
                throw e;
            }

            return serialisationResult;
        }


        /// <summary>
        /// Deserializes specified config file and loads it to Configuration field.
        /// </summary>
        /// <returns>Was the deserialisation successful and file was loaded.</returns>
        private bool DeserializeFromXML()
        {
            // Check is provided config file path correct.
            if (string.IsNullOrWhiteSpace(this.configurationFilePath))
                throw new IOException("Provided configuration file path is incorrect!");

            // Deserializing
            XmlSerializer serializer = new XmlSerializer(typeof(T));
            try
            {
                using (TextReader stream = new StreamReader(this.configurationFilePath))
                {
                    this.configuration = (T)serializer.Deserialize(stream);
                }
                return true;
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw e;
            }
        }


        /// <summary>
        /// Checks is the object serializable.
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        private static bool IsSerializable(object obj)
        {
            Type t = obj.GetType();
            return t.IsSerializable || (obj is IXmlSerializable);
        }

        #endregion

        #region ========== Properties & Fields ==========

        /// <summary>
        /// Configuration instance.
        /// </summary>
        public T Configuration
        {
            get
            {
                return this.configuration;
            }
        }
        private T configuration;


        /// <summary>
        /// Configuration file path (where configuration is written to).
        /// </summary>
        public string ConfigurationFilePath
        {
            get { return this.configurationFilePath; }
        }
        private string configurationFilePath;


        ///// <summary>
        ///// Should binary serializer be used in serialisation instead of xml.
        ///// Commonly used only with configuration containing collections.
        ///// </summary>
        //public bool UseBinarySerialisation
        //{
        //    get { return this.useBinarySerialisation; }
        //    set { this.useBinarySerialisation = value; }
        //}
        //private bool useBinarySerialisation;

        ///// <summary>
        ///// Default namespace.
        ///// </summary>
        //public string Namespace
        //{
        //    get { return configNamespace; }
        //}
        //private string configNamespace;

        #endregion

        class Constants
        {
            public const string defaultConfigFilePath = @"config.xml";
        }

    }

    class ConfigurationException : Exception
    {
        public ConfigurationException(string msg) : base(msg) { }
    }
}